package framework;

import framework.attack.BasicAttack;
import framework.attack.Teleport;
import framework.ia.Brain;
import framework.movements.LinearMovement;
import framework.movements.ParabolicStrategy;
import framework.movements.PropulsionMovement;

import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Rectangle;

public class BasicFighter extends BasicEntity implements Comparable<BasicFighter> {

	/** Max health point of the fighter */
	protected final static int MAX_HEALTH_POINT = 100;
	/** Default speeed for an horizontal move */
	protected static final int DEFAULT_SPEED = 700;
	/** Default height of the jump */
	protected static final int DEFAULT_JUMP_HEIGHT = 450;
	/** Default speed of the jump */
	protected static final int DEFAULT_JUMP_SPEED = -1800;
	/** Deault length of the invulnerability, after being hit, in ms */
	protected static final int INVULNERABILITY_LENGTH = 100;
	/** Length of the invulnerability, after being hit, in ms */
	protected int invulnerabilityLength;
	/** Length of a parry, in ms */
	protected static final int PARRY_LENGTH = 500;
	/** Length of the invulnerability flickering */
	protected static final int FLICKERING_LENGTH = 100;
	/** Current health point of the fighter */
	protected int healthPoint;
	/** Number of teleport in a fight */
	protected int nbTeleport;
	/** Number of teleport avalaible at the beginning of a fight */
	protected int maxNbTeleport;
	/** Current invulnerability status, in ms */
	protected int invulnerability;
	/** Current parry status, in ms */
	protected int parry;
	/** Ia of the fighter (optionnal) */
	protected Brain ia;
	/** Current attack being performed */
	protected BasicAttack<?> currentAttack;
	/** Current attack being performed */
	protected BasicAttack<?> teleport;
	/** Set of animations for the fighter */
	protected AnimationManager animations;
	/** The controls binding for the attacks */
	protected BasicAttackGetter attacks;
	/** Indicator for the current jump */
	protected int currentJump;
	/** Indicator for the current jump */
	protected int currentJumpY;
	/** Where the fighter is looking at */
	private boolean forward;
	/** Indicates if the fighter is parrying */
	private boolean isParrying;
	/** */
	protected String[] talks;
	/** */
	protected Stage stage;
	/** */
	protected AttackBuffer buffer;
	
	/**
	 * Creates a fighter, of position and size 0
	 */
	public BasicFighter() {
		this(0, 0);
	}

	/**
	 * Creates a fighter, of position x, y and size 0
	 * @param x x position of the fighter
	 * @param y y position of the fighter
	 */
	public BasicFighter(int x, int y) {
		this(x, y, 0 ,0);		
	}

	/**
	 * Creates a fighter, of position x, y and size width, height
	 * @param x x position of the fighter
	 * @param y y position of the fighter
	 * @param width width of the fighter
	 * @param height height of the fighter
	 */
	public BasicFighter(int x, int y, int width, int height) {
		super(x, y, width, height);
	}
	
	public void init(GameContainer gc) {
		this.healthPoint = MAX_HEALTH_POINT;
		this.ia = null;
		this.currentAttack = new BasicAttack<>();
		this.teleport = new BasicAttack<>();
		this.invulnerability =  0;
		this.parry = 0;
		this.forward = true;
		this.currentJump = 0;
		this.setMaxNbTeleport(5);
		this.nbTeleport = getMaxNbTeleport();
		this.setXspeed(0);
		this.setYspeed(0);
		if (animations != null) {
			this.animations.init();
		}
		setScale(Util.getScale(gc));
		getAttacks().init(gc);
		buffer = new AttackBuffer();
	}

	@Override
	public void draw(Graphics g, Color filter) {
		if (!currentAttack.isDrawingAttacker()) {
			if (getHealthPoint() == 0) {
				animations.getDyingAnimation(this.forward).draw(getX(), getY(), getWidth(), getHeight());
			} else if (isParrying()) {
				animations.getGuardAnimation(this.forward).draw(getX(), getY(), getWidth(), getHeight());
			} else if (isHit()) {
				animations.getHitAnimation(this.forward).draw(getX(), getY(), getWidth(), getHeight());
			} else if (isJumping()) {
				int frame = getYspeed() > 0 ? 1 : 0;
				animations.getJumpingAnimation(this.forward).setCurrentFrame(frame);
				animations.getJumpingAnimation(this.forward).draw(getX(), getY(), getWidth(), getHeight());
			} else if (isMoving()) {
				animations.getWalkingAnimation(this.forward).draw(getX(), getY(), getWidth(), getHeight());
			} else {
				animations.getIdleAnimation(this.forward).draw(getX(), getY(), getWidth(), getHeight());
			}
		}
		if(currentAttack != null && isAttacking()) {
			currentAttack.draw(g);
		}
	}
	
	@Override
	public void draw(Graphics g) {
		draw(g, null);
	}

	@Override
	public void update(GameContainer gc, int delta) {
		buffer.update(gc, delta);
		if (ia != null) {
			ia.update(gc, delta);
		}

		if (currentAttack != null && currentAttack.isActive()) {
			currentAttack.update(gc, delta);
		}

		if (this.invulnerability != 0 && this.invulnerability < invulnerabilityLength) {
			this.invulnerability += delta;
		} else if (this.invulnerability >= invulnerabilityLength)  {
			this.invulnerability = 0;
		}

		// updating position
		if (move.isActive()) {
			move.update(gc, delta);
		}
		if (move.isActive() && move instanceof LinearMovement) {
			setX(move.getX());
			setY(move.getY());
			setYspeed(Math.abs(DEFAULT_JUMP_SPEED));
		} else if (!isParrying() && !getCurrentAttack().isBlocking() || move.isActive() && move instanceof PropulsionMovement) {
			if (isJumping()) {
				int newSpeed = (int) (getYspeed() / getScale() + (GRAVITY_FACTOR * delta / 1000) * getScale());
				setYspeed(newSpeed == 0 ? 1 : newSpeed);
				setY((int) (getY() + getStepYSpeed(delta)));
			}
			if(getHealthPoint() > 0) {
				setX(isJumping() ? (int)(getX() + getStepXSpeed(delta) * 1.2) : getX() + getStepXSpeed(delta));
			}
		}
	}

	/**
	 * Triggers the jump
	 * @param groundHeight
	 */
	public boolean jump(int groundHeight) {
		boolean canJump = !isJumping() && !this.currentAttack.isBlocking() && getHealthPoint() > 0;
		if (canJump) {
			setYspeed(DEFAULT_JUMP_SPEED);
			this.currentJump =  0;
		}
		return canJump;
	}

	/**
	 * Triggers an horizontal move
	 * @param forward true to move forward, false to move backward
	 */
	public boolean move(boolean forward) {
		boolean canMove = !isHit() && !this.currentAttack.isBlocking() && !move.isActive();
		if (canMove) {
		    int xSpeed = forward ? DEFAULT_SPEED : -DEFAULT_SPEED;
		    setXspeed(xSpeed);
		}
		return canMove;
	}

	/**
	 * Stops an horizontal move
	 */
	public void stop() {
		setXspeed(0);
	}

	/**
	 * Affetcs an ia to this fighter. This fighter will not be controllable
	 * anymore.
	 * @param brain
	 */
	public void setBrain(Brain brain) {
		this.ia = brain;
	}

	/**
	 * Tests if the fighter is controllable or an ia is managing it.
	 * @return
	 */
	public boolean isControllable() {
		return this.ia == null;
	}

	/**
	 * Get this fighter name. By default, returns the class name.
	 * For a specific name, you must override this.
	 * @return the name of this fighter
	 */
	public String getFighterName() {
		return getClass().getSimpleName();
	}

	@Override
	public int compareTo(BasicFighter o) {
		return o.getHealthPoint() - this.healthPoint;
	}

	@Override
	public Rectangle getHitBox() {
		int offset = (int) (getWidth() / 2.25);
		int height = (int) (getHealthPoint() >= 0 ? getHeight() - 20 * getScale() : (getHeight() - 20 * getScale()) / 2);
		return new Rectangle(getX() + offset, getY() + 20 * getScale(), 
				             getWidth() - (offset * 2), height);
	}

	/**
	 * Triggers a hit 
	 * @param power power of the attack hitting the fighter
	 */
	public void hit(int power) {
		hit(power, INVULNERABILITY_LENGTH);
	}
	
	/**
	 * Triggers a hit 
	 * @param power power of the attack hitting the fighter
	 */
	public void hit(int power, int duration) {
		if (this.invulnerability == 0) {
			this.invulnerabilityLength = duration;
			this.healthPoint = Math.max(0, this.healthPoint - power);
			this.invulnerability++;
		}
	}

	/**
	 * Triggers a parry
	 * @param power power of the attack being parried
	 */
	public void parry(int power) {
		this.healthPoint = Math.max(0, this.healthPoint - 3);
	}
	
	/**
	 * Interrupts a jump
	 */
	public void interruptJump() {
		setYspeed(1);
	}

	/**
	 * Perform an attack
	 * @param a attack to perform
	 */
	public boolean attack(BasicAttack<?> a) {
		boolean canAttack = !isAttacking() && !isParrying() && getHealthPoint() > 0;
		if (canAttack) { // one attack at once!
			if (a instanceof Teleport || !isHit()) {
				this.currentAttack = a;
				this.currentAttack.trigger();
			}
		}
		return canAttack;
	}

	public boolean isAttacking() {
		return this.currentAttack != null && this.currentAttack.isActive();
	}

	public int getHealthPoint() {
		return healthPoint;
	}

	public void setHealthPoint(int healthPoint) {
		this.healthPoint = healthPoint;
	}

	public boolean isForward() {
		return forward;
	}

	public void setForward(boolean forward) {
		this.forward = forward;
	}

	public boolean isParrying() {
		return this.isParrying;
	}

	public boolean isHit() {
		return this.invulnerability != 0;
	}
	
	public boolean isDead() {
		return this.getHealthPoint() <= 0 && 
				animations.getDyingAnimation(this.forward).isStopped();
	}

	public BasicAttack<?> getCurrentAttack() {
		return currentAttack;
	}

	public boolean setParrying(boolean isParrying) {
		boolean canParry = !isAttacking() && !isJumping() || !isJumping() && 
				           isAttacking() && !getCurrentAttack().isBlocking();
		if (canParry || !isParrying) {
		    this.isParrying = isParrying;
		}
		return canParry;
	}

	public BasicAttackGetter getAttacks() {
		return attacks;
	}

	public void setAttacks(BasicAttackGetter attacks) {
		this.attacks = attacks;
	}

	public AnimationManager getAnimations() {
		return animations;
	}

	public void setAnimations(AnimationManager animations) {
		this.animations = animations;
	}

	public int getNbTeleport() {
		return nbTeleport;
	}

	public void setNbTeleport(int nbTeleport) {
		this.nbTeleport = nbTeleport;
	}
	
	public int getMaxNbTeleport() {
		return maxNbTeleport;
	}

	public void setMaxNbTeleport(int maxNbTeleport) {
		this.maxNbTeleport = maxNbTeleport;
	}

	public int getCurrentJump() {
		return currentJump;
	}

	public Stage getStage() {
		return stage;
	}

	public void setStage(Stage stage) {
		this.stage = stage;
	}
}